# plugs/rss.py
#
#

""" manage rss feeds """

__copyright__ = 'this file is in the public domain'

from gozerbot.persist import Persist
from gozerbot.generic import geturl, handle_exception, rlog
from gozerbot.statdict import Statdict
from gozerbot.fleet import fleet
from gozerbot.commands import cmnds
from gozerbot.examples import examples
from gozerbot.datadir import datadir
from xml.sax.handler import ContentHandler
from gozerbot.plughelp import plughelp
import gozerbot.thr as thr
import xml.sax, re, time

plughelp.add('rss', 'manage rss feeds')

savelist = []

class Rsshandler(ContentHandler):

    """ the rss sax handler """

    def __init__(self):
        ContentHandler.__init__(self)
        self.l = []
        self.result = []

    def startElement(self, name, attrs):
        """ start of element """
        self._txt = ""

    def characters(self, data):
        """ char data """
        self._txt += data.replace('\n',' ')

    def endElement(self, name):
        """ end of element """
        self.result.append((name, self._txt.replace('  ', ' ')))

class Rssitem(object):

    """ item that contains rss data """

    def __init__(self, name, url, itemslist, watchchannels=[], \
sleeptime=30*60):
        self.name = name
        self.url = url
        self.itemslist = list(itemslist)
        self.watchchannels = list(watchchannels)
        self.sleeptime = int(sleeptime)
        self.running = 0
        self.stoprunning = 0
        self.botname = None

    def __str__(self):
        return "name=%s url=%s itemslist=%s watchchannels=%s sleeptime=%s \
running=%s" % (self.name, self.url, str(self.itemslist), \
str(self.watchchannels), str(self.sleeptime), self.running)

class Rssdict(Persist):

    """ dict of rss entries """

    def __init__(self, filename):
        Persist.__init__(self, filename)
        if not self.data:
            self.data = {}
        self.handlers = {}
        for i in self.data.values():
            self.handlers[i.name] = Rsshandler()

    def add(self, name, url):
        """ add rss item """
        rlog(10, 'rss', 'adding %s %s' % (name, url))
        self.data[name] = Rssitem(name, url, ['title', ])
        self.handlers[name] = Rsshandler()
        self.save()

    def delete(self, name):
        """ delete rss item by name """
        for i in self.data.values():
            if i.name == name:
                i.running = 0
                del self.data[name]
                self.save()
 
    def byname(self, name):
        """ return rss item by name """
        for i in self.data.values():
            if i.name == name:
                return i

    def getdata(self, name):
        """ get data of rss feed """
        rssitem = self.byname(name)
        if rssitem == None:
            return []
        # fetch data from url
        try:
            urlresult = geturl(rssitem.url)
        except Exception, ex:
            rlog(10, 'rss', str(ex))
            raise
        if urlresult == None:
            return []
        # try to parse data with sax
        try:
            xml.sax.parseString(urlresult, self.handlers[rssitem.name])
            result = self.handlers[rssitem.name].result
        except Exception, ex:
            rlog(10, 'rss', "can't parse %s feed" % rssitem.name)
            self.handlers[rssitem.name] = Rsshandler()
            self.handlers[rssitem.name].result = []
            return []
        # result is cached
        self.handlers[rssitem.name].result = []
        first = rssitem.itemslist[0]
        temp1 = []
        temp2 = []
        nritems = len(rssitem.itemslist)
        # loop over result and return list of itemlist items
        for i in result:
            if i[0] == first:
                temp1 = []
                temp = i[1].strip()
                temp1.append(temp)
                continue
            if i[0] in rssitem.itemslist:
                temp = i[1].strip()
                temp1.append(temp)
            if len(temp1) == nritems:
                temp2.append(temp1)
                temp1 = []
        return temp2

class Rsswatcher(Rssdict):

    """ rss watchers """ 

    def __init__(self, filename):
        Rssdict.__init__(self, filename)
        self.results = {}

    def startwatchers(self):
        """ start watcher threads """
        for i in self.data.values():
            if i.running:
                rlog(10, 'rss', 'starting %s rss watch' % i.name)
                thr.start_new_thread(self.watch, (i.name, ))

    def stopwatchers(self):
        """ stop all watcher threads """
        for i in self.data.values():
            if i.running:
                i.stoprunning = 1

    def watch(self, name):
        """ start a watcher thread """
        length = 0
        # get basic data
        try:
            result = self.getdata(name)
            self.results[name] = result
            rssitem = self.byname(name)
            rssitem.running = 1
            rssitem.stoprunning = 0
            # keep old itemslist to see if it has changed
            olditemslist = list(rssitem.itemslist)
        except Exception, ex:
            rlog(10, 'rss', 'not starting watch reason: %s' % ex)
            return
        self.save()
        # poll every sleeptime seconds
        while rssitem.running and not rssitem.stoprunning:
            time.sleep(rssitem.sleeptime)
            try:
                if not rssitem.running or rssitem.stoprunning:
                    return
                if olditemslist != rssitem.itemslist:
                    try:
                        res = self.getdata(name)
                        if not res:
                            continue
                    except:
                        continue
                    self.results[name] = res
                    olditemslist = list(rssitem.itemslist)
                    continue
                try:
                    res = self.getdata(name)
                    if not res:
                        rlog(10, 'rss', "can't match %s data" % name)
                        continue
                except:
                    continue
                # loop over result to see if we already seen item
                for j in res:
                    if j not in self.results[name]:
                        self.results[name].append(j)
                    else:
                        continue
                    resultstr = ""
                    for item in j:
                        resultstr += "%s - " % item
                    resultstr = resultstr[:-3]
                    if not resultstr:
                        continue
                    # output new entry to watchchannels
                    for item in rssitem.watchchannels:
                        try:
                            (botname, channel) = item
                            bot = fleet.byname(botname)
                            if not bot:
                                rlog(10, 'rss', "can't find bot %s in fleet" \
% botname)
                                continue
                        except:
                            rlog(10, 'rss', '%s is not in the format \
(botname,channel)' % item)
                            continue
                        time.sleep(5)
                        try:
                            bot.say(channel, "%s: %s" % (rssitem.name, \
resultstr))
                        except:
                            handle_exception()
            except Exception, ex:
                handle_exception()

    def stopwatch(self, name):
        """ stop watcher thread """
        for i, j in self.data.iteritems():
            if i == name:
                j.running = 0
                try:
                    del self.results[name]
                except KeyError:
                    pass
                self.save()
                return 1

    def list(self):
        """ return of rss names """
        return self.data.keys()

    def runners(self):
        """ show names/channels of running watchers """
        result = []
        for i in self.data.values():
            if i.running == 1: 
                result.append((i.name, i.watchchannels))
        return result

    def url(self, name):
        """ return url of rssitem """
        for i in self.data.values():
            if i.name == name:
                return i.url

    def scan(self, name):
        """ scan a rss url for used xml items """
        try:
            result = geturl(self.url(name))
        except Exception, ex:
            rlog(10, 'rss', str(ex))
            return None
        statdict = Statdict()
        result = result.replace('\n',' ')
        all = re.findall('</*(.*?)>', result)
        for i in all:
            if i.find('?') != -1 or i.find('http') != -1:
                continue
            statdict.upitem(i)
        return statdict.top()

watcher = Rsswatcher(datadir + '/rss')

def init(): 
    """ called after plugin import """
    thr.start_new_thread(watcher.startwatchers, ())
    return 1
    
def shutdown():
    """ called before plugin import """
    watcher.stopwatchers()
    return 1
    
def handle_rssadd(bot, ievent):
    """ rss-add <name> <url> .. add a rss item """
    try:
        (name, url) = ievent.args
    except ValueError:
        ievent.missing('<name> <url>')
        return
    watcher.add(name, url)
    ievent.reply('rss item added')

cmnds.add('rss-add', handle_rssadd, ['RSS', 'OPER'])
examples.add('rss-add', 'rss-add <name> <url> to the rsswatcher','rss-add \
slashdot http://slashdot.org/slashdot.xml')

def handle_rssdel(bot, ievent):
    """ rss-del <name> .. delete a rss item """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    watcher.delete(name)
    ievent.reply('rss item deleted')

cmnds.add('rss-del', handle_rssdel, ['RSS', 'OPER'])
examples.add('rss-del', 'rss-del <name> .. remove <name> from the \
rsswatcher', 'rss-del slashdot')

def handle_rsswatch(bot, ievent):
    """ rss-watch <name> .. start watcher thread """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss object" % name)
        return
    if not rssitem.running:
        if not ievent.channel:
            ievent.reply('no channel provided')
        elif (bot.name,ievent.channel) not in rssitem.watchchannels:
            rssitem.watchchannels.append((bot.name, ievent.channel))
        thr.start_new_thread(watcher.watch, (name, ))
        ievent.reply('watcher thread started')
    else:
        ievent.reply('already watching %s' % name)

cmnds.add('rss-watch', handle_rsswatch, ['RSS', 'OPER'])
examples.add('rss-watch', 'rss-watch <name> .. go watching <name>', \
'rss-watch slashdot')

def handle_rsschannels(bot, ievent):
    """ rss-channels <name> .. show channels of rss feed """
    try:
        (name, ) = ievent.args
    except ValueError:
        ievent.missing("<name>") 
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss object" % name)
        return
    ievent.reply(rssitem.watchchannels)

cmnds.add('rss-channels', handle_rsschannels, ['RSS', 'OPER'])
examples.add('rss-channels', 'rss-channels <name> .. show channels', \
'rss-channels slashdot')

def handle_rssaddchannel(bot, ievent):
    """ rss-addchannel <name> [<botname>] <channel> .. add a channel to \
        rss item """
    botname = None
    try:
        (name, botname, channel) = ievent.args
    except ValueError:
        try:
            (name, channel) = ievent.args
        except ValueError:
            ievent.missing('<name> [<botname>] <channel>')
            return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss object" % name)
        return
    if not botname:
        if channel in rssitem.watchchannels:
            ievent.reply('we are already monitoring %s on %s' % \
(name, ievent.channel))
            return
    else:
        if (botname, channel) in rssitem.watchchannels:
            ievent.reply('we are already monitoring %s on (%s,%s)' % \
(name, botname, ievent.channel))
            return
    if not botname:
        rssitem.watchchannels.append(channel)
    else:
        rssitem.watchchannels.append((botname, channel))
    watcher.save()
    ievent.reply('rss channel added')

cmnds.add('rss-addchannel', handle_rssaddchannel, ['RSS', 'OPER'])
examples.add('rss-addchannel', 'rss-addchannel <name> [<botname>] <channel> \
..add <channel> or <botname> <channel> to watchchannels of <name>', \
'1) rss-addchannel slashdot #dunkbots 2) rss-addchannel slashdot main \
#dunkbots')

def handle_rssdelchannel(bot, ievent):
    """ rss-delchannel <name> [<botname>] <channel> .. delete channel \
        from rss item """
    botname = None
    try:
        (name, botname, channel) = ievent.args
    except ValueError:
        try:
            (name, channel) = ievent.args
            botname = None
        except ValueError:
            ievent.missing('<name> [<botname>] <channel>')
            return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss object" % name)
        return
    if not botname:
        if  channel not in rssitem.watchchannels:
            ievent.reply('we are not monitoring %s on %s' % \
(name, ievent.channel))
            return
    else:
        if (botname, channel) not in rssitem.watchchannels:
            ievent.reply('we are not monitoring %s on (%s,%s)' % \
(name, botname, ievent.channel))
            return
    if not botname:
        rssitem.watchchannels.remove(channel)
        ievent.reply('rss channel deleted')
    else:
        rssitem.watchchannels.remove((botname, channel))
        ievent.reply('rss channel deleted')
    watcher.save()

cmnds.add('rss-delchannel', handle_rssdelchannel, ['RSS', 'OPER'])
examples.add('rss-delchannel', 'rss-delchannel <name> [<botname>] \
<channel> .. delete <channel> or <botname> <channel> from watchchannels of \
<name>', '1) rss-delchannel slashdot #dunkbots 2) rss-delchannel slashdot \
main #dunkbots')

def handle_rssstopwatch(bot, ievent):
    """ rss-stopwatch <name> .. stop a watcher thread """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    if not watcher.stopwatch(name):
        ievent.reply("can't stop %s watcher" % name)
        return
    ievent.reply('stopped %s rss watch' % name)

cmnds.add('rss-stopwatch', handle_rssstopwatch, ['RSS', 'OPER'])
examples.add('rss-stopwatch', 'rss-stopwatch <name> .. stop polling <name>', \
'rss-stopwatch slashdot')

def handle_rsssleeptime(bot, ievent):
    """ rss-sleeptime <name> .. get sleeptime of rss item """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    try:
        ievent.reply('sleeptime for %s is %s seconds' % (name, \
str(rssitem.sleeptime)))
    except AttributeError:
        ievent.reply("can't get sleeptime for %s" % name)

cmnds.add('rss-sleeptime', handle_rsssleeptime, ['RSS', 'OPER'])
examples.add('rss-sleeptime', 'rss-sleeptime <name> .. get sleeping time \
for <name>', 'rss-sleeptime slashdot')

def handle_rsssetsleeptime(bot, ievent):
    """ rss-setsleeptime <name> <seconds> .. set sleeptime of rss item """
    try:
        (name, sec) = ievent.args
        sec = int(sec)
    except ValueError:
        ievent.missing('<name> <seconds>')
        return
    if sec < 60:
        ievent.reply('min is 60 seconds')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    rssitem.sleeptime = sec
    if rssitem.sleeptime > sec and rssitem.running:
        watcher.stopwatch(name)
        thr.start_new_thread(watcher.watch, (name))
    watcher.save()
    rssitem.sleeptime = sec
    ievent.reply('sleeptime set')

cmnds.add('rss-setsleeptime', handle_rsssetsleeptime, ['RSS', 'OPER'])
examples.add('rss-setsleeptime', 'rss-setsleeptime <name> <seconds> .. set \
sleeping time for <name> .. min 60 sec', 'rss-setsleeptime slashdot 600')

def handle_rssget(bot, ievent):
    """ rss-get <name> .. fetch rss data """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    try:
        result = watcher.getdata(name)
    except Exception, ex:
        ievent.reply(str(ex))
        return
    resultstr = ""
    teller = 1
    for i in result:
        tempstr = ""
        for item in i:
            tempstr += "%s - " % item
        tempstr = tempstr[:-3]
        resultstr += "%s) %s " % (teller, tempstr.strip())
        teller += 1
    if resultstr:
        ievent.reply(resultstr)
    else:
        ievent.reply("can't match watcher data")

cmnds.add('rss-get', handle_rssget, ['RSS', 'OPER'])
examples.add('rss-get', 'rss-get <name> .. get data from <name>', \
'rss-get slashdot')

def handle_rssrunning(bot, ievent):
    """ rss-running .. show which watchers are running """
    result = watcher.runners()
    resultstr = ""
    teller = 1
    for i in result:
        resultstr += "%s) %s %s " % (str(teller), i[0], i[1])
        teller += 1
    if resultstr:
        ievent.reply(resultstr)
    else:
        ievent.reply('nothing running yet')

cmnds.add('rss-running', handle_rssrunning, ['RSS', 'OPER'])
examples.add('rss-running', 'rss-running .. get running rsswatchers', \
'rss-running')

def handle_rsslist(bot, ievent):
    """ rss-list .. return list of rss items """
    result = watcher.list()
    ievent.reply(str(result))

cmnds.add('rss-list', handle_rsslist, ['RSS', 'OPER'])
examples.add('rss-list', 'get list of rss items', 'rss-list')

def handle_rssurl(bot, ievent):
    """ rss-url <name> .. return url of rss item """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    result = watcher.url(name)
    ievent.reply(str(result))

cmnds.add('rss-url', handle_rssurl, ['RSS', 'OPER'])
examples.add('rss-url', 'rss-url <name> .. get url from rssitem with \
<name>', 'rss-url slashdot')

def handle_rssadditemslist(bot, ievent):
    """ rss-additemslist <name> <item> .. add item to itemslist of rss item """
    try:
        (name, item) = ievent.args
    except ValueError:
        ievent.missing('<name> <item>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    rssitem.itemslist.append(item)
    watcher.save()
    ievent.reply('item added to itemslist')

cmnds.add('rss-additemslist', handle_rssadditemslist, ['RSS', 'OPER'])
examples.add('rss-additemslist', 'rss-additemslist <name> <item> .. add to \
itemslist of <name> ', 'rss-additemslist slashdot url')

def handle_rssdelitemslist(bot, ievent):
    """ rss-delitemslist <name> <item> .. delete item from itemslist """
    try:
        (name, item) = ievent.args
    except ValueError:
        ievent.missing('<name> <item>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    if item in rssitem.itemslist:
        rssitem.itemslist.remove(item)
        watcher.save()
        ievent.reply('item deleted from itemslist')
    else:
        ievent.reply("%s doesn't have %s in itemslist" % (name, item))

cmnds.add('rss-delitemslist', handle_rssdelitemslist, ['RSS', 'OPER'])
examples.add('rss-delitemslist', 'rss-delitemslist <name> <item> .. \
delete from itemslist of <name> ', 'rss-delitemslist slashdot url')

def handle_rssitemslist(bot, ievent):
    """ rss-itemslist <name> .. show itemslist of rss item """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    rssitem = watcher.byname(name)
    if rssitem == None:
        ievent.reply("we don't have a %s rss item" % name)
        return
    ievent.reply(str(rssitem.itemslist))

cmnds.add('rss-itemslist', handle_rssitemslist, ['RSS', 'OPER'])
examples.add('rss-itemslist', 'rss-itemslist <name> .. get itemslist of \
<name> ', 'rss-itemslist slashdot')

def handle_rssscan(bot, ievent):
    """ rss-scan <name> .. scan rss item for used xml items """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    result = watcher.scan(name)
    if result == None:
        ievent.reply("can't get data for %s" % name)
        return
    resultstr = ""
    for i in result:
        resultstr += "%s = %s " % i
    ievent.reply(resultstr)

cmnds.add('rss-scan', handle_rssscan, ['RSS', 'OPER'])
examples.add('rss-scan', 'rss-scan <name> .. get items of <name> ', \
'rss-scan slashdot')

def handle_rsssync(bot, ievent):
    """ rss-sync <name> .. sync rss item data """
    try:
        name = ievent.args[0]
    except IndexError:
        ievent.missing('<name>')
        return
    try:
        result = watcher.getdata(name)
        watcher.results[name] = result
        ievent.reply('%s synced' % name)
    except Exception, ex:
        ievent.reply("ERROR: %s" % str(ex))

cmnds.add('rss-sync', handle_rsssync, ['RSS', 'OPER'])
examples.add('rss-sync', 'rss-sync <name> .. sync data of <name>', \
'rss-sync slashdot')
